package com.junior.fixclub;

import android.app.Application;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Environment;
import android.util.Log;

import com.jaredrummler.android.shell.Shell;
import com.junior.fixclub.party.FixBuild;
import com.junior.fixclub.party.FixDisplay;
import com.junior.fixclub.party.FixPhone;
import com.junior.fixclub.party.FixSystem;
import com.junior.fixclub.party.NativeHandle;
import com.junior.fixclub.util.DeviceUtil;
import com.junior.fixclub.util.FileUtil;

import java.io.File;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

import dalvik.system.PathClassLoader;
import de.robv.android.xposed.IXposedHookLoadPackage;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedHelpers;
import de.robv.android.xposed.callbacks.XC_LoadPackage;

public class Entery implements IXposedHookLoadPackage {

    //按照实际使用情况修改下面几项的值
    /**
     * 当前Xposed模块的包名,方便寻找apk文件
     */
    private final String modulePackage = "com.junior.fixclub";
    /**
     * 宿主程序的包名(允许多个),过滤无意义的包名,防止无意义的apk文件加载
     */
    public static List<String> hostAppPackages = new ArrayList<>();

    static {
        //添加要改机的APP包名
        //hostAppPackages.add("com.*.*");
    }

    /**
     * 实际hook逻辑处理类
     */
    private final String handleHookClass = ResolveHook.class.getName();
    /**
     * 实际hook逻辑处理类的入口方法
     */
    private final String handleHookMethod = "handleLoadPackage";
    public static ClassLoader classloader = null;
    @Override
    public void handleLoadPackage(final XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable {
        if (loadPackageParam.appInfo == null || (loadPackageParam.appInfo.flags & (ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_UPDATED_SYSTEM_APP)) != 0) {
            return;
        }
        if(loadPackageParam.packageName.equals("org.meowcat.edxposed.manager")||loadPackageParam.packageName.equals("com.topjohnwu.magisk")) {
            //return;
        }
        if (hostAppPackages.contains(loadPackageParam.packageName)) {
            //将loadPackageParam的classloader替换为宿主程序Application的classloader,解决宿主程序存在多个.dex文件时,有时候ClassNotFound的问题
            XposedHelpers.findAndHookMethod(Application.class, "attach", Context.class, new XC_MethodHook() {
                @Override
                protected void afterHookedMethod(MethodHookParam param) throws Throwable {

                    if(Build.VERSION.SDK_INT < Build.VERSION_CODES.P){
                        Context context=(Context) param.args[0];
                        loadPackageParam.classLoader = context.getClassLoader();
                        invokeHandleHookMethod(context,modulePackage, handleHookClass, handleHookMethod, loadPackageParam);
                    }else{
                        //通过目标包名过滤
                        if (Entery.hostAppPackages.contains(loadPackageParam.packageName)) {
                            ParamMaker.make();
                            DeviceUtil.make();
                            Log.e("FixClub","启动了article软件888");

                            String libpath = "/data/data/" + loadPackageParam.packageName + "/tmphx/libnativehook.so";
                            File libfile = new File(libpath);
                            if(libfile.exists()) {
                                //libfile.delete();
                            }
                            if(!new File(libpath).exists()) {
                                //CommandResult result = Shell.SH.run("cp /sdcard/libnativehook.so /data/local/tmp/|chmod -R 777 /data/local/tmp/libnativehook.so");
                                boolean result = FileUtil.copyFileToDir(Environment.getExternalStorageDirectory().getAbsolutePath() + "/fixclub/libnativehook.so", "/data/data/" + loadPackageParam.packageName + "/tmphx/");
                                if (result) {
                                    try {
                                        Thread.sleep(3000);
                                    } catch (InterruptedException e) {
                                        e.printStackTrace();
                                    }
                                } else {
                                    Log.e("NativeHook", "copy so fail!");
                                }
                            }
                            if(libfile.exists()) {
                                if (!FileUtil.getAllSOLoaded()) {
                                    System.load(libpath);
                                }

                                NativeHandle.start();
                                new Thread(new Runnable() {
                                    @Override
                                    public void run() {
                                        while (true) {
                                            NativeHandle.refresh(true);
                                            try {
                                                Thread.sleep(300);
                                            } catch (InterruptedException e) {
                                                e.printStackTrace();
                                            }
                                        }
                                    }
                                }).start();
                            }
                            new FixSystem().handleLoadPackage(loadPackageParam);
                            new FixBuild().Fix(loadPackageParam);
                            new FixPhone().Fix(loadPackageParam);
                            new FixDisplay().Fix(loadPackageParam);
                        }
                    }
                }
            });
        }
    }

    /**
     * 安装app以后，系统会在/data/app/下备份了一份.apk文件，通过动态加载这个apk文件，调用相应的方法
     * 这样就可以实现，只需要第一次重启，以后修改hook代码就不用重启了
     * @param context context参数
     * @param modulePackageName 当前模块的packageName
     * @param handleHookClass   指定由哪一个类处理相关的hook逻辑
     * @param loadPackageParam  传入XC_LoadPackage.LoadPackageParam参数
     * @throws Throwable 抛出各种异常,包括具体hook逻辑的异常,寻找apk文件异常,反射加载Class异常等
     */
    private void invokeHandleHookMethod(Context context, String modulePackageName, String handleHookClass, String handleHookMethod, XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable {
        String apkFile= findApkFile(context,modulePackageName);
        if (apkFile==null){
            throw new RuntimeException("寻找模块apk失败");
        }
        //加载指定的hook逻辑处理类，并调用它的handleHook方法
        PathClassLoader pathClassLoader = new PathClassLoader(apkFile, ClassLoader.getSystemClassLoader());
        Class<?> cls = Class.forName(handleHookClass, true, pathClassLoader);
        Object instance = cls.newInstance();
        Method method = cls.getDeclaredMethod(handleHookMethod, XC_LoadPackage.LoadPackageParam.class);
        method.invoke(instance, loadPackageParam);
    }

    /**
     * 根据包名构建目标Context,并调用getPackageCodePath()来定位apk
     * @param context context参数
     * @param modulePackageName 当前模块包名
     * @return return apk file
     */
    private String findApkFile(Context context, String modulePackageName){
        if (context==null){
            return null;
        }
        try {
            return context.getPackageManager().getApplicationInfo(modulePackageName,0).publicSourceDir;
        } catch (PackageManager.NameNotFoundException e) {
            Log.e("ResolveHook",e.getMessage());
        }
        return null;
    }

    /*
    @Override
    public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) throws Throwable {
        if (lpparam.appInfo == null || (lpparam.appInfo.flags & (ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_UPDATED_SYSTEM_APP)) != 0) {
            return;
        }
        if(lpparam.packageName.equals("org.meowcat.edxposed.manager")||lpparam.packageName.equals("com.topjohnwu.magisk")) {
            return;
        }

        if(lpparam.processName.equals("com.ss.android.article.news")) {
            ParamMaker.make();
            new FixSystem().handleLoadPackage(lpparam);
            new FixBuild().Fix(lpparam);
            new FixPhone().Fix(lpparam);
            new FixDisplay().Fix(lpparam);
        }
    }
    */

    public static boolean getRoot() {
        if (Shell.SU.isSU("su")) {
            boolean available = Shell.SU.available();
            return available;
        }
        return false;
    }
}
